﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using BabyShop.WebSite.Exts.Extensions;

namespace BabyShop.WebSite.Data
{
    public static class EntityHelper
    {
        private static string GetKeyName<T>()
        {
            PropertyInfo[] propertyInfos = typeof(T).GetProperties();
            var selectQuery =
                propertyInfos.FirstOrDefault(p => p.IsDefined(typeof(KeyAttribute), false));

            return selectQuery != null ? selectQuery.Name : null;
        }

        public static string GetKeyName<T>(this object source)
        {
            return GetKeyName<T>();
        }

        public static object GetKeyValue<T>(this object source, string keyName)
        {
            if (string.IsNullOrEmpty(keyName))
                keyName = source.GetKeyName<T>();
            var keyProperty = source.GetType().GetProperty(keyName);
            return keyProperty != null ? keyProperty.GetValue(source, null) : null;
        }

        public static TKeyType GetKeyValue<T, TKeyType>(this object source, string keyName = null)
        {
            var keyValue = source.GetKeyValue<T>(keyName);
            if (!(keyValue is TKeyType))
                throw new Exception("the return type is not equal with the key's type.");
            return (TKeyType) keyValue;
        }

        #region Private expression tree helpers

        private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType) {
            // 创建一个参数传入的lambda表达式 (Entity => Entity.OrderByField).
            var parameter = Expression.Parameter(typeof(TEntity), "Entity");
            //  创建一个选择器，支持子选择 如 User.Dept.ID
            PropertyInfo property;
            Expression propertyAccess;
            if (propertyName.Contains('.')) {
                // 判断是否为子字段
                String[] childProperties = propertyName.Split('.');
                property = typeof(TEntity).GetProperty(childProperties[0]);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);
                for (int i = 1; i < childProperties.Length; i++) {
                    property = property.PropertyType.GetProperty(childProperties[i]);
                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
                }
            }
            else {
                property = typeof(TEntity).GetProperty(propertyName);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);
            }
            resultType = property.PropertyType;
            // 创建排序lambda表达式
            return Expression.Lambda(propertyAccess, parameter);
        }

        private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable source, string methodName, String fieldName) {
            Type type = typeof(TEntity);
            Type selectorResultType;
            LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
            MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                            new[] { type, selectorResultType },
                            source.Expression, Expression.Quote(selector));
            return resultExp;
        }
        #endregion

        public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string fieldName) {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "OrderBy", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> OrderByDescending<TEntity>(this IQueryable<TEntity> source, string fieldName) {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> ThenBy<TEntity>(this IOrderedQueryable<TEntity> source, string fieldName) {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "ThenBy", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> ThenByDescending<TEntity>(this IOrderedQueryable<TEntity> source, string fieldName) {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "ThenByDescending", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> OrderUsingSortExpression<TEntity>(this IQueryable<TEntity> source, string sortExpression) 
        {
            String[] orderFields = sortExpression.Split(',');
            IOrderedQueryable<TEntity> result = null;
            for (int currentFieldIndex = 0; currentFieldIndex < orderFields.Length; currentFieldIndex++) {
                String[] expressionPart = orderFields[currentFieldIndex].Trim().Split(' ');
                String sortField = expressionPart[0];
                Boolean sortDescending = (expressionPart.Length == 2) && (expressionPart[1].Equals("DESC", StringComparison.OrdinalIgnoreCase));
                if (sortDescending) {
                    result = currentFieldIndex == 0 ? source.OrderByDescending(sortField) : result.ThenByDescending(sortField);
                }
                else {
                    result = currentFieldIndex == 0 ? source.OrderBy(sortField) : result.ThenBy(sortField);
                }
            }
            return result;
        }

        public static EntityPager<TEntity> ToEntityPager<TEntity>(this IQueryable<TEntity> source, int? pageIndex, int pageSize = 10, string sortField = "", string sortKey = "ASC")
        {
            return new EntityPager<TEntity>(source, pageIndex ?? 1, pageSize, sortField, sortKey);
        }

        public static T FirstOrDefaultByKeyValue<T>(this IQueryable<T> source, object keyValue)
        {
            string keyName = GetKeyName<T>();
            if (string.IsNullOrEmpty(keyName))
                return default(T);

            var parameter = Expression.Parameter(typeof(T), typeof(T).Name.ToLower());
            var propertyExpression = parameter.Property(keyName);
            var expr = propertyExpression.Equal(Expression.Constant(keyValue));

            return source.First(Expression.Lambda<Func<T, bool>>(expr, parameter));
        }
    }
}